package com.gitee.srurl.core.crontab;

import cn.hutool.core.collection.CollectionUtil;
import com.gitee.srurl.core.api.IDataStore;
import com.gitee.srurl.core.cache.ShortUrlVisitTimeCache;
import com.gitee.srurl.core.dto.RefreshRemainRecordDTO;
import com.gitee.srurl.core.factory.ShortUrlFactory;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 刷新剩余访问次数任务
 * 可以需要接入已有的定时任务如xxl-job、qrtz、elastic-job等，不多引入依赖
 *
 * @author zxw
 * @date 2023/5/13 16:35
 */
@Slf4j
@Component
@EnableScheduling
@RequiredArgsConstructor
public class RefreshRemainingVisitTimesTask {

    /**
     * 剩余次数缓存
     * key:短链ID，value:剩余次数
     */
    private static final Cache<String, Long> remainTimesCache = CacheBuilder.newBuilder()
            // 一小时内 如果次数相同 不更新 但是如果更新了次数 刷新了数据  但是剩余次数有和上次剩余次数一样 有缓存就不会更新 这就不对了
            // 但是这种情况应该是不会很多的 所以缓存时间不能太长
            .expireAfterWrite(1, TimeUnit.HOURS)
            // 最多缓存2048条数据 酌情根据实际情况调整
            .maximumSize(2048)
            .build();

    /**
     * 5分钟更新一次
     */
    @Scheduled(cron = "0 0/5 * * * ?")
    public void refresh() {

        IDataStore dataStore = ShortUrlFactory.getShortUrlConfig().getDataStore();
        // 查询最近一天内有更新的数据 禁用的也要查询 还是需要把最新的数据刷回去
        List<RefreshRemainRecordDTO> recentOneDayHadVisitRecord = dataStore.getRecentOneDayHadVisitRecord();
        if (CollectionUtil.isEmpty(recentOneDayHadVisitRecord)) {
            return;
        }

        List<RefreshRemainRecordDTO> needUpdateRecordList = new ArrayList<>();
        // 临时缓存 只有保证更新成功 才会去刷新缓存 否则不会去刷新 如果失败了还加缓存 就会导致更新延迟1小时
        Map<String, Long> tempCache = new HashMap<>(1 << 4);
        for (RefreshRemainRecordDTO refreshRemainRecordDTO : recentOneDayHadVisitRecord) {
            String shortId = refreshRemainRecordDTO.getShortId();
            long times = ShortUrlVisitTimeCache.get(shortId);
            // 返回0 说明可能是不存在 或者就是没次数了
            if (times == 0) {
                boolean exists = ShortUrlVisitTimeCache.exists(shortId);
                // 如果key不存在 说明很可能是无限次数的 即使有次数现限制 缓存失效也会从store中读取 所以跳过就行
                if (!exists) {
                    continue;
                }
            }

            Long cacheTimes = remainTimesCache.getIfPresent(shortId);
            if (cacheTimes != null && cacheTimes.equals(times)) {
                // 次数相同 不更新
                continue;
            }

            refreshRemainRecordDTO.setRemainTimes(times);
            needUpdateRecordList.add(refreshRemainRecordDTO);
            // 添加临时缓存 如果更新成功 后面把这些数据刷到缓存中
            tempCache.put(shortId, times);
        }

        // 更新次数
        if (CollectionUtil.isNotEmpty(needUpdateRecordList)) {
            dataStore.refreshRecordRemainTimes(needUpdateRecordList);
        }
        // 更新成功 添加缓存
        for (Map.Entry<String, Long> entry : tempCache.entrySet()) {
            remainTimesCache.put(entry.getKey(), entry.getValue());
        }
    }
}
